Skip to content

fix(ui): theme-aware charts + heat-pump card auto re-discovery#532

Merged
frahlg merged 19 commits into
srcfl:masterfrom
HuggeK:fix/dashboard-light-dark
Jul 10, 2026
Merged

fix(ui): theme-aware charts + heat-pump card auto re-discovery#532
frahlg merged 19 commits into
srcfl:masterfrom
HuggeK:fix/dashboard-light-dark

Conversation

@HuggeK

@HuggeK HuggeK commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Two dashboard fixes, independent of the heat-pump driver work.

Charts now render in light mode

The canvas-drawn power/energy charts hard-coded dark-theme colors (axis text, gridlines, zero/now/hover lines, both tooltips' bg+border+text, and the neutral "Load" line + legend swatch), so they went invisible/wrong on a light background. Added a small resolver that reads the CSS theme tokens (--fg, --fg-dim, --fg-muted, --line, --ink-raised, --accent-e) into concrete canvas colors per draw — cached, and re-read when data-theme flips. Saturated data-series hues are unchanged (they read on both themes).

Heat-pump card auto re-discovery

heating.js ran discovery once and cached it forever — and [] is truthy, so a site that discovered before its pump reported hp_power_w stayed blank until a manual reload. It now re-scans on first load and every 5 min; steady-state polling still only touches already-known heat-pump drivers.

Verification

  • node --check passes on both files.
  • DESIGN.md-compliant: chart chrome uses tokens, no new hard-coded hex.

Draft for review.

🤖 Generated with Claude Code

claude and others added 3 commits June 26, 2026 14:13
The live charts hard-coded dark-theme colors for canvas chrome (axis
text, gridlines, both tooltips, the neutral load line), so they went
invisible/wrong on a light background. Add a resolver that reads the CSS
theme tokens into concrete canvas colors (cached, invalidated on
data-theme change) and use it for all chart chrome; saturated series
hues are unchanged.

heating.js cached heat-pump discovery once and never re-checked (and an
empty [] is truthy, so it stuck) — a pump added while the dashboard was
open never appeared without a reload. Re-discover on first load and
every 5 min.

Co-Authored-By: HuggeK <[email protected]>
… + cache-bust

The earlier fix only touched app.js, but index.html loads next-app.js —
app.js renders only on /legacy.html. So the live dashboard's charts kept
their hard-coded dark chrome and stayed dark/invisible in light mode.

Mirror the theme resolver (cssColor + cached chartColors) into next-app.js
and apply it to all canvas chrome: the power/energy chart (renderChart),
both hover tooltips, and the 24h battery+SoC live-history chart — axis
text, gridlines, zero/now/hover lines, tooltip bg+border+text, and the
neutral Load line. Saturated data-series hues are unchanged.

Bump the cache-bust tokens (next-app.js next19->next20, heating.js
heat1->heat2) so browsers actually re-fetch the updated next-app.js and
the already-shipped heating.js re-discovery fix instead of serving the
stale cached copies.

Co-Authored-By: HuggeK <[email protected]>
Each row in the per-driver 'all signals' drill-in now shows the signal's
source Modbus register id (m.register from the driver detail metrics, supplied
by the NIBE driver / emit_metric register plumbing). Signals with no Modbus
mapping render '—'. Bumps heating.js cache token heat2->heat3 so browsers pick
up the new column.

Co-Authored-By: HuggeK <[email protected]>
@HuggeK
HuggeK force-pushed the fix/dashboard-light-dark branch from 88cf9f4 to 62c0d4b Compare June 26, 2026 13:16
claude and others added 9 commits June 29, 2026 10:48
The heat-pump card renders into a bare .card, whose base rule (style.css)
sets background:var(--surface). --surface is hard-coded dark in :root and is
NOT flipped by html[data-theme=light], so the card stayed dark on a light page
while the type-specific cards (.summary-card etc., themed via --ink-raised) went
light. Add a scoped next.css rule putting .heating-row .card on the --ink-raised
palette, and bump next.css cache token next6->next7.

Co-Authored-By: HuggeK <[email protected]>
…tips

Replaces the 4-tile card with a grouped layout (Effekt & el · Temperaturer ·
Ventilation · Drift · Energi). Each tile shows the NIBE sensor designation in
parens (BT21, BE1, GP1 …) and an info icon whose hover tooltip explains the
term. Adds 'Total effekt nu' (whole-pump draw) distinct from compressor-only
power. Bumps heating.js heat3->heat4.

Co-Authored-By: HuggeK <[email protected]>
…power-limit tiles

Card labels, tooltips and group titles are now English. Priority shows a word
(Off / Hot water / Heating / …) instead of the raw code. The per-tile help
icon is a '?' circle matching the existing .plan-help-btn style (border, sizing,
accent hover) instead of the custom ⓘ. Adds Power limit / Fuse / Max add. heat
tiles. heat4->heat5.

Co-Authored-By: HuggeK <[email protected]>
Each group (Power & electrical, Temperatures, …) is now a distinct grey
sunken sub-card matching the dashboard's in-card box pattern (--ink-sunken +
--line border + radius + padding), so the groups read as separate cards under
the heat-pump card. heat5->heat6.

Co-Authored-By: HuggeK <[email protected]>
…form

The form treated NIBE as an OAuth (apicreds) driver and seeded
{client_id, client_secret}. Distinguish local basic-auth drivers (apicreds +
connection_defaults.port) and seed {host, username, password} instead. The HTTP
fieldset now renders Username + a Certificate fingerprint (tls_pin_sha256) field
for these drivers; password comes from config_secrets. allowed_hosts is
auto-derived from config.host by the backend (port-insensitive), so no manual
allowlist needed. devices.js tabsplit2->tabsplit3.

Co-Authored-By: HuggeK <[email protected]>
Step 2: a 30-day multi-line SVG chart (outdoor BT1 / supply BT2 / return BT3,
distinct hues, themed axis) under the tiles. Step 3: an Energy-per-period box
(Today / This week / This month / This year, consumed vs produced) computed
client-side as counter deltas across calendar boundaries; periods without enough
logged history show 'accumulating'. Both fetch via /api/series (needs the
generic parseRange added in the nibe branch for 30d/366d windows). heat6->heat7.

Co-Authored-By: HuggeK <[email protected]>
Each row in the All-signals detail now carries an always-visible sub-line
explaining what the signal is: the curated card-tile description when one exists,
otherwise the device's own point title (m.title, newly served by the API). Keeps
the 'All signals' button. heat7->heat8.

Co-Authored-By: HuggeK <[email protected]>
…fields

Card: clicking a ? help icon no longer navigates into the all-signals
detail (onGridClick ignores .ftw-hp-i); the native title tooltip explains the
metric in place. Devices tab: the local-HTTP Username + cert-fingerprint inputs
now use the same render-then-reveal pattern as the battery field, so they appear
for apicreds drivers (NIBE) even when the first render beats the async catalog
fetch — previously they were dropped. Tokens heat8->heat9, tabsplit3->tabsplit4.

Co-Authored-By: HuggeK <[email protected]>
The battery field was relying on the [hidden] attribute, which any author
CSS rule on .field-row children can override. Switch to an inline display:none
(beats any selector) on both the initial render and the post-catalog toggle, so
the NIBE heat-pump (catalog caps: apicreds, no battery) never shows it. Token
tabsplit4->tabsplit5.

Co-Authored-By: HuggeK <[email protected]>
@HuggeK

HuggeK commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

What this PR does — NIBE heat-pump card + light-mode fixes

A full NIBE S-series heat-pump telemetry card on the dashboard, plus light-theme fixes across the live charts.

Light/dark theming

  • Live power/energy charts (next-app.js) resolve CSS theme tokens into concrete canvas colours per draw, so axis chrome, gridlines, tooltips and the neutral "Load" line render correctly in light mode (they were hard-coded for dark).
  • The heat-pump card now uses --ink-raised like every other card — it was using the non-flipping --surface, so it stayed dark on a light page.

Heat-pump card (heating.js)

  • Curated headline metrics in grey sub-cards (power/electrical, temperatures, ventilation, operation, lifetime energy), each with a ? tooltip; technical sensor names in parentheses (BT1, BE1, …).
  • 30-day temperature chart (outdoor / supply / return, distinct hues).
  • Energy-per-period box (Today / This week / This month / This year, consumed vs produced) from the lifetime kWh counters; sparse periods show "accumulating".
  • "All signals" detail: every ~960 registers with its Modbus register id and an always-visible plain-language explanation (curated text, else the device's own NIBE point title — backend in feat(drivers): NIBE S-series local REST API driver + opt-in HTTP TLS cert pinning #530).
  • ? help icons are tooltip-only — a click no longer navigates into the all-signals view.

Devices tab (settings/tabs/devices.js)

  • NIBE local-REST driver configurable in the UI: Host/IP, Username (HTTP Basic auth), self-signed cert SHA-256 fingerprint (pin).
  • Local-API credential inputs use a render-then-reveal pattern keyed on the driver's apicreds capability, so they appear even when the first render beats the async /api/drivers/catalog fetch.
  • "Battery capacity (kWh)" is hidden for non-battery drivers (the pump declares no battery capability) via an inline display:none that no CSS rule can override.
Verification

Verified live on the test Pi: driver healthy (status OK, polling, 963 metrics all carrying titles + register ids), 30-day chart + ~4 days of energy history rendering, devices-tab fixes confirmed in the running container. Depends on #530 (NIBE driver + emit_metric title arg + generic /api/series range parsing).

claude and others added 2 commits June 30, 2026 13:50
Pairs with the api fix that placeholders a set-but-blanked driver config
secret: /api/config now returns the masked placeholder for a saved password, so
the badge reads as set. Reworded so a masked secret isn't mistaken for missing.
Token tabsplit5->tabsplit6.

Co-Authored-By: HuggeK <[email protected]>
The api blanks config.password but exposes a driver-level has_password
flag (config.go MaskSecrets). The Secrets badge only checked the blanked
config value, so a saved NIBE password showed as unset. Read has_password for
the password key. No backend change needed. Token tabsplit6->tabsplit7.

Co-Authored-By: HuggeK <[email protected]>
@HuggeK

HuggeK commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

🔗 Paired with #530 (NIBE telemetry driver)

Heads-up: the "independent of the heat-pump driver work" line in the description is now outdated — this PR grew a full NIBE heat-pump card that depends on #530:

  • The card + "all signals" view render the telemetry that feat(drivers): NIBE S-series local REST API driver + opt-in HTTP TLS cert pinning #530's drivers/nibe_local.lua emits, and rely on its backend additions — emit_metric's title arg (per-signal explanations), generic /api/series range parsing (30-day chart + energy periods), and the Devices-tab setup for the local-REST driver.
  • The original two fixes (theme-aware charts + card re-discovery) remain and are driver-agnostic.

Best reviewed / merged together with #530.

claude and others added 4 commits June 30, 2026 15:49
The 30-day temp chart had only faint gridlines (and the SVG text it did
have was stretched by preserveAspectRatio=none). Render a proper y-axis (°C,
top/mid/bottom) and x-axis (dates across the span) as HTML around the SVG so the
labels stay crisp. heat9->heat10.

Co-Authored-By: HuggeK <[email protected]>
Replace the axis-less sparkline on the card with a dedicated powerChartBlock:
0-based kW y-axis + HH:MM time x-axis, reusing the temp chart's HTML-axis layout.
sparkline() is untouched so the ~937 tiny per-signal detail charts stay minimal.
heat10->heat11.

Co-Authored-By: HuggeK <[email protected]>
The ~937 detail sparklines were unlabelled. Add a compact per-sparkline
min/max (its y-scale) beside each, widen the trend column to fit, and note the
shared 24h window in a modal subtitle. sparkline() stays shared/minimal.
heat11->heat12.

Co-Authored-By: HuggeK <[email protected]>
…imestamp

Heat-pump card: (1) the 24h power chart now plots Total drawn / Compressor /
Internal add. heat (kW, with legend) instead of compressor-only; (2) the temp
chart gains an Extract air (BT20) line and BT sensor codes in the legend
(Outdoor BT1 / Supply BT2 / Return BT3), plus a 'supply/return = the heating loop
(in-floor / radiators)' subtitle; (3) a 'data as of <time>' stamp under the card
header showing the latest poll. heat12->heat13.

Co-Authored-By: HuggeK <[email protected]>

@frahlg frahlg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed theme handling, NIBE dashboard rendering, settings fields, escaping, and request behavior. Added maintainer fixes to cache long-range history, suppress overlapping refreshes, and limit detail sparklines to curated signals instead of issuing ~980 concurrent series requests. All 226 web tests, local verify-all, and GitHub CI are green; #530 is now on master.

@frahlg
frahlg merged commit e8b4bf4 into srcfl:master Jul 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants